package me.yangbajing.web
package snippet

import scala.xml.{Elem, Text, NodeSeq}

import net.liftweb.http.{S, SHtml, Templates, DispatchSnippet}
import net.liftweb.util.Helpers._
import net.liftweb.common.{Failure, Full, Loggable}
import net.liftweb.http.js.{JsCmds, JE}
import net.liftweb.util.{ClearNodes, CssSel}
import org.bson.types.ObjectId

import yangbajing.util.Imports.Y

import me.yangbajing.model._
import me.yangbajing.helper.{MeHelpers}
import yangbajing.util.MarkdownHelpers

object DocumentSnippet extends DispatchSnippet with Loggable {
  def dispatch = {
    case "panel" => panel
    case "newest" => newest
    case "datas" => datas
    case "title" => title
    case "show" => show
    case "edit" => edit
    case "comments" => comments
    case "comment" => comment
  }

  def panel(nodeSeq: NodeSeq): NodeSeq = {
    val curPage = H.param("doc_page", 1)
    val limit = H.param("doc_limit", H.defaultLimit)
    val creator = H.param("creator")
    val gteCreatedAt = H.paramDateTime("gte_created_at")
    val lteCreatedAt = H.paramDateTime("lte_created_at")
    val categoryId = H.param("category_id") map {
      case v if ObjectId.isValid(v) => v
      case v => MCategory.getIdByName(v) openOr v
    }
    val tagId = H.param("tag_id")

    val page = MDocument.pagination(curPage, limit, creator, gteCreatedAt, lteCreatedAt, categoryId, tagId)

    val cssSel =
      "data-id=heading" #> (
        ".badge *" #> page.total
        ) &
        "#doc-items li" #> page.page.map(doc => documentItem(doc, nodeDocumentItem)) &
        ".pagination" #> page.pagination("", pageKey = "doc_page", limitKey = "doc_limit")

    cssSel(nodeSeq)
  }

  def documentItem(doc: MDocument, nodeSeq: NodeSeq): NodeSeq = {
    val tagsSel = doc.tagIds.map(tag => H.href(W.hrefTag(tag), tag, "target" -> "_blank"))

    val docCommentSel = ".doc-comment" #> (".badge *" #> doc.comment_count)

    val docMeta =
      <div>
        由
        <a href={W.hrefUser(doc.creator)} target="_blank">
          {doc.creator}
        </a>{MeHelpers.timeDescription(doc.created_at)}&nbsp;
        被贴了
        {tagsSel}
        标签。
      </div>

    val cssSel =
      ".box-side" #> (
        "a [href]" #> W.hrefUser(doc.creator) &
          "img [src]" #> MUser.avatarUri(doc.creator)
        ) &
        ".box-main" #> (
          docCommentSel &
            ".title *" #> H.href(W.hrefDocument(doc.slugOrId), doc.title, "target" -> "_blank") &
            ".text-muted *" #> doc.subtitle.getOrElse("") &
            ".doc-meta *" #> docMeta &
            ".doc-preview *" #> doc.description.map(MarkdownHelpers.parseToNodeSeq).getOrElse(NodeSeq.Empty)
          )

    cssSel(nodeSeq)
  }

  def newest(nodeSeq: NodeSeq): NodeSeq = {
    val limit = S.attr("limit") flatMap asInt openOr H.defaultLimit

    val docs = MDocument.idTitles(limit)

    val cssSel =
      ".me-list-unstyled" #> (
        "li" #> docs.map {
          case (id, title, slug) =>
            "a [href]" #> W.hrefDocument(slug getOrElse id) &
              "a [title]" #> id &
              "a *" #> title
        })

    cssSel(nodeSeq)
  }

  def datas(nodeSeq: NodeSeq): NodeSeq = {
    val curPage = H.param("doc_page", 1)
    val limit = H.param("doc_limit", H.defaultLimit)
    val creator = H.param("creator")
    val gteCreatedAt = H.paramDateTime("gte_created_at")
    val lteCreatedAt = H.paramDateTime("lte_created_at")

    val page = MDocument.pagination(curPage, limit, creator, gteCreatedAt, lteCreatedAt)
    val cssSel =
      "#doc-total *" #> page.total &
        "#doc-datas tr" #> _datas(page.page) &
        "#doc-pagination" #> page.pagination("", pageKey = "doc_page", limitKey = "doc_limit")

    cssSel(nodeSeq)
  }

  private def _datas(docs: List[MDocument]): List[Elem] =
    docs.map {
      doc =>
        val trId = nextFuncName
        val removeElem = H.buttonDialog(trId, Text("删除"), Text("删除文档"), Text(s"确认删除文档：${doc.id} ？")) {
          MDocument.removeById(doc.id)
          $("#" + trId).remove().cmd
        }

        <tr>
          <td>
            {hrefDoc(doc.id, doc.title)}
          </td>
          <td>
            {hrefDoc(doc.id, doc.title)}
          </td>
          <td>
            {hrefDoc(doc.id, doc.creator)}
          </td>
          <td>
            {hrefDoc(doc.id, doc.category)}
          </td>
          <td>
            {hrefDoc(doc.id, Y.formatDateTime.print(doc.created_at))}
          </td>
          <td>
            {removeElem}
          </td>
        </tr>
    }

  private def hrefDoc(docId: String, content: String): Elem =
    hrefDoc(docId, content)

  private def hrefDoc(docId: String, content: Text): Elem =
    <a href={W.hrefDocument(docId)} target="_blank">
      {content}
    </a>

  def title(nodeSeq: NodeSeq): NodeSeq = {
    val v = W.reqDocument.is match {
      case Full(doc) => doc.title
      case _ if H.param("document_id").exists(_ == "") => "创建文档"
      case _ => "文档"
    }

    <title data-lift="head">
      {v + " - " + MeHelpers.config.site.name}
    </title>
  }

  def show(nodeSeq: NodeSeq): NodeSeq = {
    val cssSel =
      for {
        doc <- W.reqDocument.is
      } yield _show(doc)

    cssSel match {
      case Full(cs) =>
        cs(nodeSeq)

      case Failure(msg, _, _) =>
        <h1 class="text-danger">
          {msg}
        </h1>

      case _ =>
        NodeSeq.Empty
    }
  }

  private def _show(doc: MDocument): CssSel = {
    val editSel =
      W.theAccount.is match {
        case Full(account) if account.id == doc.creator => "href=edit [href]" #> W.hrefDocumentEdit(doc.slugOrId)
        case _ => "href=edit" #> ClearNodes
      }

    val favoriteSel =
      W.theAccount.is match {
        case Full(account) if MFavorite.findOne(doc.id, account.id).isDefined =>
          "#me-doc-favorite" #> (
            "a *" #> "取消收藏" &
              "input [value]" #> "cancel")
        case Full(account) =>
          "#me-doc-favorite" #> (
            "a *" #> "收藏" &
              "input [value]" #> "")
        case _ =>
          "#me-doc-favorite" #> ClearNodes
      }

    val meta = s"发布于: %s，被收藏：%d次" format(Y.formatDateTime.print(doc.created_at), doc.favorite_count)

    val docMetaSel =
      ".doc-meta" #> (
        "@meta" #> meta &
          favoriteSel &
          "@comment" #> s"评论(${doc.comment_count})"
        )

    val tagsSel =
      "#me-doc-tags a" #> MTag.findAll(0, 10, refId = Some(doc.id)).map(tag =>
        "a [href]" #> W.hrefTag(tag.tag) &
          ".label *" #> tag.tag
      )

    "h1 *" #> doc.title &
      editSel &
      "#me-doc-subtitle *" #> doc.subtitle.getOrElse("") &
      "#me-doc-id [value]" #> doc.id &
      docMetaSel &
      tagsSel &
      "#me-doc-content *" #> MarkdownHelpers.parseToNodeSeq(doc.content)
  }

  def edit(nodeSeq: NodeSeq): NodeSeq = {
    val cssSel =
      for {
        account <- W.theAccount.is
      } yield _edit(account)

    cssSel map (_ apply nodeSeq) openOr <h1 class="text-danger">账户会话信息不存在</h1>
  }

  private def _edit(account: Account): CssSel = {
    val reqDoc = W.reqDocument.is.map(ReqDoc.apply) openOr ReqDoc(account.id)
    val categories = MCategory.idNames()
    val modalId = nextFuncName

    val previewSel =
      W.reqDocument.is match {
        case Full(doc) => "href=preview [href]" #> W.hrefDocument(doc.slugOrId)
        case _ => "title=preview" #> ClearNodes
      }

    previewSel &
      "#doc-categories" #> SHtml.untrustedSelect(categories, Full(reqDoc.category), reqDoc.category = _) &
      "@new-category [href]" #> s"#$modalId" &
      _addCategory(modalId, account, "#doc-categories") &
      "@title" #> SHtml.text(reqDoc.title, reqDoc.title = _) &
      "@subtitle" #> SHtml.text(reqDoc.subtitle.getOrElse(""), v => reqDoc.subtitle = Y.option(v)) &
      "@slug" #> SHtml.text(reqDoc.slug.getOrElse(""), v => reqDoc.slug = Y.option(v)) &
      "@content" #> SHtml.textarea(reqDoc.content, reqDoc.content = _) &
      "@tags" #> SHtml.text(reqDoc.tags.mkString(","), v => reqDoc.tags = v.split(',').toList.map(_.trim.toLowerCase).filterNot(_ == "")) &
      "@submit" #> SHtml.hidden(() =>
        W.reqDocument.is match {
          case Full(doc) => // edit
            val removeTags = reqDoc.removeTags(doc.tagIds)
            val insertTags = reqDoc.insertTags(doc.tagIds)

            val result = MDocument.modify(doc.id,
              category = Y.option(reqDoc.category),
              title = Y.option(reqDoc.title),
              description = reqDoc.description,
              slug = reqDoc.slug,
              content = Y.option(reqDoc.content),
              subtitle = reqDoc.subtitle,
              tags = if (reqDoc.tags.isEmpty) None else Some(reqDoc.tags.mkString(",")),
              removeTags = removeTags,
              insertTags = insertTags)

            result match {
              case Full(1) => JsCmds.Alert("编辑文档成功") & JsCmds.RedirectTo(W.hrefDocumentEdit(reqDoc.slug getOrElse doc.id))
              case Failure(msg, _, _) => JsCmds.Alert("编辑文档失败：" + msg)
              case _ => JsCmds.Alert("编辑文档失败")
            }

          case _ => // create
            MDocument.insert(reqDoc.toMDocument, reqDoc.tags) match {
              case Full(ret) =>
                JsCmds.Alert("创建文档成功") & JsCmds.RedirectTo(W.hrefDocumentEdit(ret.slug getOrElse ret.id))
              case Failure(msg, _, _) =>
                JsCmds.Alert("创建文档失败：" + msg)
              case _ =>
                JsCmds.Alert("创建文档失败")
            }
        })
  }

  private def _addCategory(modalId: String, account: Account, exp: String): CssSel = {
    var value = ""

    val modalSel =
      ".modal [id]" #> modalId &
        "@category" #> SHtml.text(value, value = _) &
        "@submit" #> SHtml.hidden(() =>
          MCategory.insert(value, account.id) match {
            case Full(_) =>
              val options = MCategory.idNames().map {
                case (id, name) if name == value =>
                  <option selected="selected" value={id}>
                    {name}
                  </option>

                case (id, name) =>
                  <option value={id}>
                    {name}
                  </option>
              }
              $(exp).html(options).cmd &
                $("#" + modalId).modal("hide").cmd

            case _ =>
              JE.Call("formControlError", "#control-add-category", "添加分类失败：分类已存在或无效。")
          })

    ".modal" #> modalSel
  }

  def comment(nodeSeq: NodeSeq): NodeSeq = {
    val cssSel =
      for {
        doc <- W.reqDocument.is
      } yield _comment(doc)

    cssSel map (_ apply nodeSeq) openOr NodeSeq.Empty
  }

  private def _comment(doc: MDocument): CssSel = {
    var guestEmail = ""
    var guestName = ""
    var content = ""

    val guestSel =
      W.theAccount.is match {
        case Full(account) =>
          "#guest-comment" #> ClearNodes

        case _ =>
          "#guest-comment" #> (
            "@guest-name" #> SHtml.text(guestName, guestName = _) &
              "@guest-email" #> SHtml.text(guestEmail, guestEmail = _)
            )
      }

    guestSel &
      "@comment" #> SHtml.textarea(content, content = _) &
      "@submit" #> SHtml.hidden(() => {
        val result = W.theAccount.is match {
          case Full(account) => MComment.insert(doc.id, account.id, content)
          case _ => MComment.insert(doc.id, guestName, content, email = Y.option(guestEmail))
        }

        result match {
          case Full(c) =>
            MDocument.incrementCommentCount(doc.id)
            $("#doc-comments").append(commentItem(c, nodeCommentItem, None)).cmd &
              JE.Call("formControlClear", "#control-doc-comment").cmd

          case Failure(msg, _, _) =>
            JE.Call("formControlError", "#control-doc-comment", msg).cmd

          case _ =>
            JE.Call("formControlError", "#control-doc-comment", "发表评论失败或评论为空").cmd
        }
      })
  }

  def comments(nodeSeq: NodeSeq): NodeSeq = {
    val cssSel =
      for {
        doc <- W.reqDocument.is
      } yield _comments(doc)

    cssSel map (_ apply nodeSeq) openOr NodeSeq.Empty
  }

  private def _comments(doc: MDocument): CssSel = {
    val curPage = H.param("cur_page", 1)
    val limit = H.param("limit", H.defaultLimit)

    val commentPage = MComment.pagination(curPage, limit, refId = Some(doc.id))

    var idx = (curPage - 1) * limit

    ".panel-heading" #> (
      "#doc-comment-size *" #> commentPage.total
      ) &
      "#doc-comments li" #> commentPage.page.map {
        c =>
          idx += 1
          commentItem(c, nodeCommentItem, Some(idx))
      } &
      ".pagination" #> commentPage.pagination(W.hrefDocument(doc.slugOrId))
  }

  def commentItem(c: MComment, nodeSeq: NodeSeq, idx: Option[Int]): NodeSeq = {
    val sideSel =
      ".box-size" #> (
        "a [href]" #> W.hrefUser(c.creator) &
          "img [src]" #> MUser.avatarUri(c.creator)
        )

    val docMeta: NodeSeq =
      idx.map(i => Text(s"${i}楼：")).getOrElse(NodeSeq.Empty) ++
        H.href(W.hrefUser(c.creator), c.creator, "target" -> "_blank") ++
        Text(" 发表于 " + Y.formatDateMinus.print(c.created_at))

    val mainSel =
      ".box-main" #> (
        ".doc-meta *" #> docMeta &
          ".doc-preview *" #> MarkdownHelpers.parseToNodeSeq(c.content)
        )

    (sideSel & mainSel) apply nodeSeq
  }

  def nodeCommentItem = Templates(List("h", "document", "_comment_item")).openOrThrowException("/h/document/_comment_item not found!")

  def nodeDocumentItem = Templates(List("h", "document", "_document_item")).openOrThrowException("/h/document/_document_item not found!")
}
